﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;

namespace SBPweb.PROvibe.Shared.Serialization
{

    /// <summary>
    /// Code originally authored by Rockford Lhotka:
    /// http://www.lhotka.net/weblog/SilverlightSerialization.aspx,
    /// presented here with minor naming and code changes.
    /// </summary>
    public class SerializationInfo
    {

        internal const string TOKEN_ROOT = "g"; //default was "g"
        internal const string TOKEN_OBJECT = "o"; //default was "o"
        internal const string TOKEN_REFERENCEID = "i"; //default was "i"
        internal const string TOKEN_TYPENAME = "t"; //default was "t"
        internal const string TOKEN_NAME = "n"; //default was "n"
        internal const string TOKEN_VALUE = "v"; //default was "v"
        internal const string TOKEN_LIST = "l"; //default was "l"
        internal const string TOKEN_REFERENCE = "r"; //default was "r"
        internal const string TOKEN_FIELD = "f"; //default was "f"

        private class ValueEntry
        {
            public string Name { get; set; }
            public object Value { get; set; }

            public ValueEntry(string name, object value)
            {
                Name = name;
                Value = value;
            }
        }

        private readonly Dictionary<string, ValueEntry> _values = new Dictionary<string, ValueEntry>();

        internal SerializationInfo(int referenceId)
        {
            ReferenceId = referenceId;
        }

        internal int ReferenceId { get; private set; }
        
        public string TypeName { get; set; }

        public void AddValue(string name, object value)
        {
            _values.Add(name, new ValueEntry(name, value));
        }

        public object GetValue(string name)
        {
            ValueEntry result;
            return _values.TryGetValue(name, out result) ? result.Value : null;
        }

        internal XElement ToXElement()
        {
            var root = new XElement(TOKEN_OBJECT);
            root.Add(new XAttribute(TOKEN_REFERENCEID, ReferenceId));
            root.Add(new XAttribute(TOKEN_TYPENAME, TypeName));
            
            foreach (var item in _values)
            {
                var info = item.Value.Value as SerializationInfo;
                if (info == null)
                {
                    var list = item.Value.Value as List<SerializationInfo>;
                    if (list == null)
                    {
                        if (item.Value.Value != null)
                            root.Add(new XElement(TOKEN_FIELD,
                                                  new XAttribute(TOKEN_NAME, item.Value.Name),
                                                  new XAttribute(TOKEN_VALUE, item.Value.Value)));
                    }
                    else
                    {
                        var listElement = new XElement(TOKEN_LIST,
                                                       new XAttribute(TOKEN_NAME, item.Value.Name));
                        foreach (var listItem in list)
                            listElement.Add(new XElement(TOKEN_REFERENCE,
                                                         new XAttribute(TOKEN_REFERENCEID, listItem.ReferenceId)));
                        root.Add(listElement);
                    }
                }
                else
                    root.Add(new XElement(TOKEN_REFERENCE,
                                          new XAttribute(TOKEN_NAME, item.Value.Name),
                                          new XAttribute(TOKEN_REFERENCEID, info.ReferenceId)));
            }
            return root;
        }

        internal SerializationInfo(XElement data)
        {
            ReferenceId = Convert.ToInt32(data.Attribute(TOKEN_REFERENCEID).Value);
            if (data.Name == TOKEN_OBJECT)
            {
                TypeName = data.Attribute(TOKEN_TYPENAME).Value;
            }
        }

        internal void Deserialize(XElement data, XmlFormatter formatter)
        {
            foreach (var item in data.Elements())
            {
                if (item.Name == TOKEN_FIELD)
                {
                    var entry = new ValueEntry(item.Attribute(TOKEN_NAME).Value, item.Attribute(TOKEN_VALUE).Value);
                    _values.Add(entry.Name, entry);
                }
                else if (item.Name == TOKEN_LIST)
                {
                    var listItems = new List<SerializationInfo>();
                    foreach (var content in item.Elements())
                        listItems.Add(new SerializationInfo(content));

                    var entry = new ValueEntry(item.Attribute(TOKEN_NAME).Value, listItems);
                    _values.Add(entry.Name, entry);
                }
                else
                {
                    var referenceId = Convert.ToInt32(item.Attribute(TOKEN_REFERENCEID).Value);
                    var entry = new ValueEntry(
                        item.Attribute(TOKEN_NAME).Value, 
                        new SerializationInfo(referenceId));
                    _values.Add(entry.Name, entry);
                }
            }
        }
    }
}